WasmtimeでWebAssemblyを動かしてみる
Introduction
最近各所で盛り上がってきているWebAssembly。
ブラウザでJavaScriptと連携させて高速処理を実行したりするプログラムです。
(ブラウザ以外で実行環境としての活用も少しつづ進みつつある)
今回はWasmtimeを使ってWebAssemblyプログラムを動かしてみます。
Confirmation of terms
まずは基本的な用語の確認をしておきましょう。
WebAssembly(WASM)?
WebAssemblyは、ブラウザ上においてネイティブレベルのパフォーマンスで
動作することを目的に策定されたバイナリフォーマットです。
C/C++/Rustなどのプログラム言語をコンパイルしてWASMとして動かすことができます。
GoogleやMozzilaなど、いろいろなTech企業によって仕様策定が進められています。
現状ではブラウザ上で動かすことが多いですが、
最近はそれ以外の環境で動かすことも増えてきました。
ブラウザ以外の実行環境において、(OSリソースへアクセスするためなど)WebAssemblyの
インターフェイスを策定するのが、後述するWASIです。
WASI?
WASIとは、WebAssembly System Interfaceの略で、
WebAssemblyをブラウザ以外の環境で実行するためのシステムインターフェイスです。
このインターフェイスによって、WASMがOSのリソースにアクセスできます。
一般的にいうWASMがWebAssemblyのcore apiにJavascriptインターフェイスと
Web apiを加えたもので、WASIはcore apiにWASI APIを加えたもののようです。
- 参考:https://zenn.dev/newgyu/scraps/ffbce244b960e6
WASIの実装として、WasmerやWasmEdge、
本稿で紹介するWasmtimeなどがあります。
Wasmtime?
Wasmtimeとは、WebAssembly用の軽量&コンパクトなランタイムです。
Rustで実装されてます。
Wasmtimeは、規模に関係なくどんなアプリでも実行できるよう、
埋め込み可能なランタイムを目指して鋭意開発中とのことです。
ちなみに、ここではwasmtimeをつかってWASMを実行してます。
Environment
- MacBook Pro (13-inch, M1, 2020)
- OS : MacOS 11.3.1
- rust : 1.61.0
Setup
Wasmtimeはcurlを使ってインストールします。
% curl https://wasmtime.dev/install.sh -sSf | bash Installing latest version of Wasmtime (v0.37.0) Checking for existing Wasmtime installation Fetching archive for macOS, version v0.37.0 https://github.com/bytecodealliance/wasmtime/releases/download/v0.37.0/wasmtime-v0.37.0-aarch64-macos.tar.xz ######################################################################## 100.0% Creating directory layout Extracting Wasmtime binaries x wasmtime-v0.37.0-aarch64-macos/ x wasmtime-v0.37.0-aarch64-macos/wasmtime x wasmtime-v0.37.0-aarch64-macos/LICENSE x wasmtime-v0.37.0-aarch64-macos/README.md Finished installation. Open a new terminal to start using Wasmtime!
wasmtimeコマンドが実行できればインストールOKです。
% wasmtime -V wasmtime-cli 0.37.0
Try
では、WebAssemblyを動かしてみましょう。
ここではRustをつかってwasmファイルを作成します。
WebAssemblyのバイナリをRustでビルドするには普通に
RustがインストールされていればOKです。
(WebAssembly用のビルドターゲットは必要)
rustcでビルド
まずはシンプルにrustcでコンパイルしてみます。 main.rsを下記のように作成。
fn main() { println!("Hello, world from rustc"); }
wasm32-wasiターゲットを追加して、rustcでコンパイル。
% rustup target add wasm32-wasi % rustc main.rs --target wasm32-wasi
main.wasmが生成されているので、wasmtimeで実行。
% wasmtime main.wasm Hello, world from rustc
Cargoでビルド
次はCargoでビルドしてみます。
% cargo new hello-wasmtime && cd hello-wasmtime
src/main.rsを修正。
fn main() { println!("Hello, world from cargo"); }
target指定してビルド&実行。
% cargo build --target wasm32-wasi % wasmtime target/wasm32-wasi/debug/hello-wasmtime.wasm Hello, world from cargo
なお、これらのwasmファイルは別環境(Linuxとか)にもっていっても、
そのまま動かすことができます。
なんという Write once, run anywhere。
[Appendix] WASM on Deno
ここでは、Rustのコードをwasm
にコンパイルしてDenoで動かしています。
同じようにやってみましょう。
Denoをインストール
まずは↓のようにDenoのインストールをします。
% curl -fsSL https://deno.land/x/install/install.sh | sh もしくは % brew install deno
WebAssemblyバイナリの作成
wasmファイルを生成するためにRustプロジェクトを作成します。
新しいターゲットも追加する必要があるのでtarget addします。
% cargo new wasm-deno --lib Created library `wasm-deno` package #さっきのwasm32-wasiとは違うターゲット % rustup target add wasm32-unknown-unknown
Cargo.tomlは下記のようにします。
WASMとjsのブリッジ用ツール、wasm-bindgenを追加しています。
[package] name = "wasm-deno" version = "0.1.0" edition = "2021" [dependencies] wasm-bindgen = "0.2.80" [lib] name = "sayhello" crate-type =["cdylib", "lib"] [[bin]] name = "mybin" path = "src/main.rs"
スタンドアロンで実行する用にbinセクション、
Denoのライブラリとしてビルドする用にlibセクションを記述しています。
次に、src/lib.rsを下記のように記述。
Denoから呼び出される関数として定義します。
wasm_bindgenマクロがjsとrustのブリッジをしてくれます。
use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn say_hello() -> String { return "Hello Wasm on Deno".to_string(); }
動作確認用にmain.rsを記述してみます。
use sayhello::say_hello; fn main() -> std::io::Result<()> { let message = say_hello(); println!("{}", message); Ok(()) }
say_hello関数は普通に実行可能です。
% cargo run --bin mybin Compiling wasm-deno v0.1.0 Finished dev [unoptimized + debuginfo] target(s) in 0.26s Running `target/debug/mybin` Hello Wasm on Deno
wasmファイルを生成するため、libとtargetを指定してビルド実行します。
% cargo build --lib --target wasm32-unknown-unknown
ビルドすると、
target/wasm32-unknown-unknown/debug/に
sayhello.wasmが生成されます。
そして、wasm-bindgenでDeno用にwasmのブリッジファイルを生成。
% mkdir server % wasm-bindgen --target deno ./target/wasm32-unknown-unknown/debug/sayhello.wasm --out-dir ./server
server/main.tsファイルで簡易httpサーバ機能を作成して、
say_hello関数を呼んでみましょう。
import { serve } from "https://deno.land/[email protected]/http/server.ts"; import { say_hello } from "./sayhello.js"; async function reqHandler(req: Request) { let message = say_hello(); return new Response(message); } serve(reqHandler, { port: 5000 });
ちゃんとJavaScript経由で実行できています。
% deno run --allow-read --allow-net --allow-env ./server/main.ts % curl http://localhost:5000 Hello Wasm on Deno
なお、lib.rsのuseとwasm_bindgenマクロをコメントアウトして、
さきほどと同じくwasm32-wasiをtargetとしてビルドすれば、
wasmtimeで実行可能です。